/*
 * @Author: yc yangcheng960813@163.com
 * @Date: 2022-09-24 17:12:25
 * @LastEditTime: 2023-04-20 13:25:40
 * @LastEditors: yayalee yangcheng960813@163.com
 * @Description: 
 * @FilePath: \dark_slash\assets\Cocos-libs\extend\Extend.js
 * 可以输入预定的版权声明、个性签名、空行等
 */

window.ii = window.ii || {};
window.cc = window.cc || {};

//#region console.log 模块的封装
(function (exports) {
    'use strict';

    let Level;
    (function (Level) {
        Level[Level["DEBUG"] = 0] = "DEBUG";
        Level[Level["INFO"] = 1] = "INFO";
        Level[Level["WARN"] = 2] = "WARN";
        Level[Level["ERROR"] = 3] = "ERROR";
        Level[Level["FATAL"] = 4] = "FATAL";
    })(Level = Level || (Level = {}));


    class Configs { }
    Configs.logLevel = Level.INFO;
    Configs.logTime = true;
    Configs.logMilliseconds = false;
    Configs.fatalThrow = false;
    Configs.color_Log = '#00FF00';
    Configs.color_Debug = '#08f';
    Configs.color_Info = '#D8BFD8';
    Configs.color_Warn = '#FFD700';//'#f80';
    Configs.color_Error = '#f00';
    Configs.color_Fatal = '#900';
    Configs.DEBUG = true;

    function getDateString() {
        let date = new Date();
        let time = date.getFullYear() + ':' + date.getMonth() + ':' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
        if (Configs.logMilliseconds) {
            time = time + ':' + date.getMilliseconds();
        }
        let timeStr = "<" + time + ">";
        return timeStr;
    }

    function logc() {
        if (!Configs.DEBUG) return;
        if (cc && cc.sys.isBrowser) {
            var msg = '| '
            for (let i = 0; i < arguments.length; i++) {
                var value = arguments[i];
                try {
                    value = JSON.stringify(value);
                    msg += value + ' | ';
                } catch (error) {
                    msg += value + ' | ';
                }
            }
            console.log('%c' + msg, 'color:' + Configs.color_Debug)
        } else {
            console.log(...arguments);
        }
    }

    function commonlog(category, level, ...data) {
        if (!Configs.DEBUG) return;
        if ((!(cc && cc.sys.isBrowser))) {
            !level && console.log(category || '', ...data);
            level == Level.INFO && console.info(category || '', ...data);
            level == Level.WARN && console.warn(category || '', ...data);
            level == Level.ERROR && console.error(category || '', ...data);
            return;
        }
        let output = '';
        if (level == null) {
            level = Level.INFO;
        }
        // if (Configs.logLevel > level) {
        //     return;
        // }
        let logColor = '#000';
        switch (level) {
            case Level.DEBUG:
                output = '[DEBUG]';
                logColor = Configs.color_Debug;
                break;
            case Level.INFO:
                output = '[INFO]';
                logColor = Configs.color_Info;
                break;
            case Level.WARN:
                output = '[WARN]';
                logColor = Configs.color_Warn;
                break;
            case Level.ERROR:
                output = '[ERROR]';
                logColor = Configs.color_Error;
                break;
            case Level.FATAL:
                output = '[FATAL]';
                logColor = Configs.color_Fatal;
                break;
            default:
                output = '[LOG]';
                logColor = Configs.color_Log;
                break;
        }
        if (Configs.logTime) {
            output = getDateString() + output + ' ';
        }
        if (category && category.length > 0) {
            output += '<' + category + '> ';
        }
        // output += msg;

        if (data) {
            console.log('%c ' + output, 'color:' + logColor, ...data);
        } else {
            console.log('%c ' + output, 'color:' + logColor);
        }
        if (level == Level.FATAL && Configs.fatalThrow) {
            logError(output);
        }
    }

    function debug(category, ...data) { commonlog(category, Level.DEBUG, ...data); }
    function info(category, ...data) { commonlog(category, Level.INFO, ...data); }
    function warn(category, ...data) { commonlog(category, Level.WARN, ...data); }
    function error(category, ...data) { commonlog(category, Level.ERROR, ...data); }
    function fatal(category, ...data) { commonlog(category, Level.FATAL, ...data); }
    function log(...data) { commonlog("", "", ...data); }


    function table(...a) {
        if (!Configs.DEBUG) return;
        if (cc && cc.sys.isBrowser)
            console.table(...a);
        else
            log(...a)
    }

    function logError(...a) {
        console.error(...a);
    }

    function logWarn(...a) {
        if (!Configs.DEBUG) return;
        console.warn(...a);
    }

    function time(...a) {
        if (!Configs.DEBUG) return;
        console.time(...a);
    }

    function timeStamp(...a) {
        if (!Configs.DEBUG) return;
        console.timeStamp(...a);
    }

    function timeEnd(...a) {
        if (!Configs.DEBUG) return;
        console.timeEnd(...a);
    }

    function group(...a) {
        if (!Configs.DEBUG) return;
        console.group(...a);
    }

    function groupEnd() {
        if (!Configs.DEBUG) return;
        console.groupEnd();
    }

    exports.logc = logc;
    exports.table = table;
    exports.logError = logError;
    exports.logWarn = logWarn;
    exports.time = time;
    exports.timeStamp = timeStamp;
    exports.timeEnd = timeEnd;
    exports.group = group;
    exports.groupEnd = groupEnd;

    exports.Level = Level;
    exports.Configs = Configs;
    exports.log = log;
    exports.debug = debug;
    exports.info = info;
    exports.warn = warn;
    exports.error = error;
    exports.fatal = fatal;

}(window.ii.log = window.ii.log || {}));
//#endregion

//#region cocos 装饰器的封装
(function (exports) {
    'use strict';
    /**
     * 混合方法 如果没有 会自动创建方法和属性 根据传入的对象来创建
     * @param mixins 多个对象参数
     * @returns 
     */
    function mixin(...mixins) {
        return target => {
            if (!mixins.length) {
                return console.error(`@mixin() class ${target.name} requires at least one mixin as an argument`)
            }
            for (let i = 0, l = mixins.length; i < l; i++) {
                mixinCall(mixins, target);
            }

        };
    }
    function mixinCall(mixin, target) {
        if (typeof mixin == "string") return;
        const descs = Object.getOwnPropertyDescriptors(mixin);
        const keys = Object.getOwnPropertyNames(descs);

        for (let j = 0, k = keys.length; j < k; j++) {
            const key = keys[j];
            // console.error(keys[j])
            if (!target.prototype.hasOwnProperty(key)) {
                Object.defineProperty(target.prototype, key, descs[key]);
            }
        }
    }

    /**
     * 属性绑定节点
     * @param path 
     * @returns 
     */
    function bind_node(path, type) {
        return function (target, property) {
            // console.error("bind_node", target['_name_'], property);
            let className = target.constructor.name; //cc.js.getClassName(target);继承的情况下不好使
            //如果path 是 Object 那就说明是组件
            path instanceof Object ? (type = path, path = property) : !path && (path = property);
            target[`${className}_bind_node_array`] = target[`${className}_bind_node_array`] || [];
            var array = target[`${className}_bind_node_array`];
            array.push({ key: property, path: path, type: type, class: className });
        }
    }
    function checkCtorArgoument(decorate) {
        return function (target) {
            if (typeof target === 'function') {
                // no parameter, target is ctor
                return decorate(target);
            }
            return function (ctor) {
                return decorate(ctor, target);
            };
        }
    }
    /**
     * 自动绑定节点/注册属性和方法
     * @param args 
     * @returns 
     */
    const auto_class = checkCtorArgoument(function (ctor, o) {
        if (Array.isArray(o)) {
            //@ts-ignore
            o.forEach(element => {
                mixinCall(element, ctor);
            });
        } else if (typeof o === 'object') {
            mixinCall(o, ctor);
        }
        //父类子类继承写了此装饰器就会导致调用两次继承的越多次数越多
        let first = ctor.prototype['$Super.onLoad'];
        if (first) return;
        ctor.prototype['$Super.onLoad'] = true;
        //必须得在 onLoad 里面绑定节点重写 onLoad 的方法
        var oldOnLoad = ctor.prototype.onLoad;
        // ctor.prototype['_name_'] = cc.js.getClassName(ctor.prototype);
        ctor.prototype.onLoad = function () {
            // this['_name_'] = cc.js.getClassName(this.constructor);
            let className = this.constructor.name;//cc.js.getClassName(this);
            //注册的节点信息
            var bindArray = this[`${className}_bind_node_array`];
            //过滤一下因为父类上使用了 @bind_node 导致的无关紧要的查找(父类为什么会导致所有的子类的node都到这里面来了)
            bindArray = bindArray && bindArray.filter((obj) => obj.class == className);
            //重写 onLoad 方法
            Array.isArray(bindArray) && bindArray.forEach(info => {
                //已经有节点了 跳过递归太深也是要时间的
                if (this[info.key]) return;
                var path = info.path;
                var node = this.node;
                node = findInChild(path, node);
                !node && ii.log.logError(`节点绑定错误 ::script name is ${className} 节点名字--> ${info.key} :: 路径--> ${info.path}`);
                var o = info.type ? (node && node.$(info.type)) || null : node;
                this[info.key] = o;
            });
            //初始化 比 onLoad 还要早
            if (this["__init__"]) {
                this["__init__"]();
            }
            delete this[`${className}_bind_node_array`];
            //执行原来的 onLoad 方法
            oldOnLoad && oldOnLoad.call(this);
        }
    })

    function findInChild(path, node) {
        if (typeof path == 'string' && path.indexOf('/') != -1) {
            return cc.find(path, node);
        } else {
            return find(path, node);
        }
    }

    function find(name, parent) {
        if (!parent) {
            ii.log.logError("find parent is null");
            return null;
        }
        var target = parent.getChildByName(name);
        if (target != null)
            return target;
        for (let i = 0; i < parent.childrenCount; i++) {
            target = find(name, parent.children[i]);
            if (target != null) {
                return target;
            }
        }
        return target;
    }

    /**
     * 输出方法传递过来的参数
     * @param methodName 
     * @returns 
     */
    function log_params(methodName) {
        return (target, name, descriptor) => {
            if (methodName == null) {
                methodName = `${target.constructor.name}.${name}`;
            }

            const method = descriptor.value;
            descriptor.value = function (...args) {
                ii.log.info(`(${methodName}) 正在执行: ${name}(${args}) = ?`);
                let ret;
                try {
                    ret = method.apply(this, args);
                    ii.log.info(`(${methodName}) 成功 : ${name}(${args}) => ${ret}`);
                } catch (error) {
                    ii.log.error(`(${methodName}) 失败: ${name}(${args}) => ${error}`);
                }
                return ret;
            }
        }
    };

    /**
     * 统计方法执行的时间
     * @param prefix 标签前缀 
     * @returns 
     */
    function time(prefix) {
        let count = 0;
        return function handleDescriptor(target, key, descriptor) {
            if (!ii.log.Configs.DEBUG) {
                return descriptor.value;
            }
            const fn = descriptor.value;
            if (prefix == null) {
                //混淆以后会出问题加个标记吧
                prefix = `${cc.js.getClassName(target.constructor)}-sign${Math.floor(Math.random() * 1000)}.${key}`;
            }
            if (typeof fn !== 'function') {
                throw new Error(`@time can only be used on functions, not: ${fn}`);
            }
            return {
                ...descriptor,
                value() {
                    const label = `${prefix}->${arguments ? typeof arguments[0] == 'string' ? "param|" + arguments[0] : "" : ""}|${count}|> `;
                    count = count % Number.MAX_VALUE + 1;
                    ii.log.time(label);
                    let isPromise = false;
                    try {
                        const s = fn.apply(this, arguments);
                        isPromise = s instanceof Promise;
                        isPromise && (s.finally
                            ? s.finally(() => {
                                ii.log.timeEnd(label);
                            })
                            : s.then(() => {
                                ii.log.timeEnd(label);
                            }).catch(() => {
                                ii.log.timeEnd(label);
                            }));
                        return s;
                    } finally {
                        !isPromise && ii.log.timeEnd(label);
                    }
                }
            }
        }
    }

    /**
     * 暂停方法
     * @param o 
     * @returns 
     */
    function pause(...o) {
        return function (target, name, descriptor) {
            const method = descriptor.value;
            descriptor.value = function (...args) {
                if (ii.Game.isPause) {
                    return;
                }
                method.apply(this, args);
            }
        }
    }

    exports.mixin = mixin;

    exports.bind_node = bind_node;
    exports.auto_class = auto_class;
    exports.findInChild = findInChild;

    exports.time = time;
    exports.log_params = log_params;
    exports.pause = pause;

}(window.ii = window.ii || {}));
//#endregion

//#region Game模块
(function (exports) {
    class Game { }
    Game.isPause = false;
    Game.isWin = false;

    exports.Game = Game;
}(window.ii = window.ii || {}));
//#endregion

//#region Proxy 代理类封装
(function (exports) {

    class LocalStorage { }
    /**
     * 获取缓存数据
     * @param {string} key 
     * @param {boolean} encrypt 
     * @returns 
     */
    LocalStorage.getItem = function (key, encrypt = false) {
        let data = cc.sys.localStorage.getItem(encrypt ? key + "_encrypt" : key);
        if (data) {
            return encrypt ? JSON.parse(ii.Base64.decode(data)) : JSON.parse(data);
        }
        return null;
    }
    /**
     * 保存缓存数据
     * @param {string} key 
     * @param {Object} value 
     * @param {boolean} encrypt 
     */
    LocalStorage.setItem = function (key, value, encrypt = false) {
        var str = ii.js.isString(value) ? value : JSON.stringify(value);
        encrypt ? cc.sys.localStorage.setItem(key + '_encrypt', ii.Base64.encode(str)) : cc.sys.localStorage.setItem(key, str);
    }

    class TouchEvent { }
    TouchEvent.touch_start = cc.Node.EventType.TOUCH_START;
    TouchEvent.touch_move = cc.Node.EventType.TOUCH_MOVE;
    TouchEvent.touch_end = cc.Node.EventType.TOUCH_END;
    TouchEvent.touch_cancel = cc.Node.EventType.TOUCH_CANCEL;

    exports.LocalStorage = LocalStorage;
    exports.TouchEvent = TouchEvent;
}(window.ii.proxy = window.ii.proxy || {}));
//#endregion

//#region 平台模块 platform
(function (exports) {
    'use strict';
    var proxy = null;
    function init() {
        proxy = window["miniPlatform"];
        const syncList = ['setFrameRate', 'share', 'userInfoHide', 'userInfoShow', 'userInfoDestroy', 'initVideo', 'isVideoEnable',
            'initInsert', 'loadInsert', 'hasBanner', 'nextBanner', 'createBanner', 'initBanner', 'checkBanner', 'clearDelayBanner', 'showBanner',
            'updateBanner', 'hideBanner', 'recorderStart', 'recorderStop', 'recorderPause',
            'recorderResume', 'recorderCreate', 'recorderHide', 'canShareRecorder', 'getRecorderTime', 'statusBarHeight',
            'screenWidth', 'screenHeight', 'vibrate', 'isNetValid', 'addEventShow', 'addEventHide', 'recorderClip',
            'recorderShare', 'showFavoriteGuide', 'setDefaultShare', 'updateReviveTypeInfo', 'setNativeLastShowTime',
            'initNativeAd', 'sendReqAdShowReport', 'sendReqAdClickReport', 'reportNativeAdClick', 'initGamePortalAd', 'showToast',
            'getLaunchOptions', 'getScene', 'showInsertAd', 'initBannerId', 'showOnePixelBanner', 'showShareMenu',
            'openShare', 'getReadSetting', 'playSound', 'initAppBox', 'showAppBox', 'checkBlockAd', 'showBlockAd',
            'hideBlockAd', 'destroyInsertAd', 'pauseSound', 'getAdPos', 'showCustomAd', 'hideCustomAd', 'hideGamePortalAd'];
        const asyncList = ['login', 'playVideo', 'setCloudStorage', 'getCloudStorage',
            'userInfoCreate', 'navigateToOther', 'loadSubpackage', 'getUserInfo',
            'openAwemeUserProfile', 'checkFollowAwemeState', 'loadNativeAd', 'isBeforeGameAccount', 'getAdReporteStatus',
            'showGamePortalAd', 'hasDesktopIcon', 'createDesktopIcon', 'getNetworkType', 'shareRecorderVideo', 'showMoreGamesModalSimple', 'showInterstitialAd'];

        if (proxy) { proxy.init(); }
        for (let key in proxy) {
            let func = proxy[key];
            if (func != null && typeof func === 'function') {
                if (asyncList.indexOf(key) >= 0) {
                    asyncs[key] = proxy[key];
                } else {
                    syncs[key] = proxy[key];
                }
            }
        }
        for (let i = 0, n = syncList.length; i < n; i++) {
            let key = syncList[i];
            if (syncs[key] == null) {
                switch (key) {
                    default:
                        syncs[key] = function () {
                            ii.log.debug('Platform', "Sync方法 " + key + " 在当前平台不存在");
                            return null;
                        }
                        break;
                }
            }
        }
        for (let i = 0, n = asyncList.length; i < n; i++) {
            let key = asyncList[i];
            if (asyncs[key] == null) {
                switch (key) {
                    case "playVideo":
                        asyncs[key] = function _async() {
                            return ii.Coop((resolve, reject) => {
                                ii.log.debug('Platform', "Async方法 " + key + " 在当前平台不存在");
                                resolve(true);
                            });
                        }
                        break;
                    case "hasDesktopIcon":
                        asyncs[key] = function _async() {
                            return ii.Coop((resolve, reject) => {
                                ii.log.debug('Platform', "Async方法 " + key + " 在当前平台不存在");
                                resolve(false);
                            });
                        }
                        break;
                    default:
                        asyncs[key] = function _async() {
                            return ii.Coop((resolve, reject) => {
                                ii.log.debug('Platform', "Async方法 " + key + " 在当前平台不存在");
                                reject();
                            });
                        }
                        break;
                }
            }
        }
    }

    function initAds(params) { proxy && (proxy.initAds(params)); }
    class asyncs { }
    class syncs { }

    exports.init = init;
    exports.initAds = initAds;
    exports.proxy = proxy;
    exports.async = asyncs;
    exports.sync = syncs;
}(window.ii.platform = window.ii.platform || {}));

//#endregion

//#region js 一些快捷操作的封装
(function (exports) {

    class js { }
    /**
     * 判断指定的值是否为对象
     * @param value 值
     */
    js.isObject = (value) => Object.prototype.toString.call(value) === '[object Object]';

    js.isArray = (value) => Array.isArray(value);

    /**
     * Check the obj whether is number or not
     * If a number is created by using 'new Number(10086)', the typeof it will be "object"...
     * Then you can use this function if you care about this case.
     * 
     * 检查obj是否为number
     * 如果使用'new number(10086)'创建一个数字，它的类型将是"object"…
     * 如果你关心这种情况，你可以用这个函数。
     * @method isNumber
     * @param {*} obj
     * @returns {Boolean}
     */
    js.isNumber = (obj) => typeof obj === 'number' || obj instanceof Number;

    js.isNull = (obj) => typeof obj === 'undefined' || obj == null || Number.isNaN(obj);

    js.isFunction = (obj) => typeof obj === 'function';
    /**
     * Check the obj whether is string or not.
     * If a string is created by using 'new String("blabla")', the typeof it will be "object"...
     * Then you can use this function if you care about this case.
     * 
     * 检查obj是否为string
     * 如果使用'new string ("blabla")'创建一个字符串，它的类型将是"object"…
     * 如果你关心这种情况，你可以用这个函数。
     * @method isString
     * @param {*} obj
     * @returns {Boolean}
     */
    js.isString = (obj) => typeof obj === 'string' || obj instanceof String;

    js.isEmptyString = (obj) => typeof obj === 'string' && obj.length > 0;

    /**
     * Checks whether obj is an empty object
     * @method isEmptyObject
     * @param {any} obj 
     * @returns {Boolean}
     */
    js.isEmptyObject = function (obj) {
        for (var key in obj) {
            return false;
        }
        return true;
    }

    /**
    * Removes all enumerable properties from object
    * 从对象中移除所有可枚举属性
    * @method clear
    * @param {any} obj
    */
    js.clear = function (obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
            delete obj[keys[i]];
        }
    }

    /**
     * 深拷贝
     * @param target 目标
     */
    js.deepCopy = function (target) {
        if (target == null || typeof target !== 'object') {
            return target;
        }
        let result = null;
        if (target instanceof Date) {
            result = new Date();
            result.setTime(target.getTime());
            return result;
        }
        if (target instanceof Array) {
            result = [];
            for (let i = 0, length = target.length; i < length; i++) {
                result[i] = this.deepCopy(target[i]);
            }
            return result;
        }
        if (target instanceof Object) {
            result = {};
            for (const key in target) {
                if (target.hasOwnProperty(key)) {
                    result[key] = this.deepCopy(target[key]);
                }
            }
            return result;
        }
        console.warn(`不支持的类型：${result}`);
    }

    /**
     * 拷贝对象
     * @param target 目标
     */
    js.copy = function (target) {
        return JSON.parse(JSON.stringify(target));
    }

    /**
     * 随机打乱数组 es5 写法
     * @param arr 
     */
    js.shuffle = function (arr) {
        var i = arr.length, t, j;
        while (--i) {
            j = Math.floor(Math.random() * i);
            t = arr[i];
            arr[i] = arr[j];
            arr[j] = t;
        }
        return arr;
    }

    // see https://github.com/petkaantonov/bluebird/issues/1389
    js.shiftArguments = function () {
        var len = arguments.length - 1;
        var args = new Array(len);
        for (var i = 0; i < len; ++i) {
            args[i] = arguments[i + 1];
        }
        return args;
    }

    /**
     * Removes the array item at the specified index.
     * 删除指定索引处的数组项。
     * @method removeAt
     * @param {any[]} array
     * @param {Number} index
     */
    js.removeAt = function (array, index) {
        array.splice(index, 1);
    }

    /**
     * Removes the array item at the specified index.
     * 删除指定索引处的数组项。
     * It's faster but the order of the array will be changed.
     * 这样更快，但是数组的顺序会改变。
     * @method fastRemoveAt
     * @param {any[]} array
     * @param {Number} index
     */
    js.fastRemoveAt = function (array, index) {
        var length = array.length;
        if (index < 0 || index >= length) {
            return;
        }
        array[index] = array[length - 1];
        array.length = length - 1;
    }

    /**
     * Removes the first occurrence of a specific object from the array.
     * 从数组中删除特定对象的第一个出现项。
     * @method remove
     * @param {any[]} array
     * @param {any} value
     * @return {Boolean}
     */
    js.remove = function (array, value) {
        var index = array.indexOf(value);
        if (index >= 0) {
            this.removeAt(array, index);
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Removes the first occurrence of a specific object from the array.
     * It's faster but the order of the array will be changed.
     * 
     * 从数组中删除特定对象的第一个出现项。
     * 这样更快，但是数组的顺序会改变。
     * @method fastRemove
     * @param {any[]} array
     * @param {Number} value
     */
    js.fastRemove = function (array, value) {
        var index = array.indexOf(value);
        if (index >= 0) {
            array[index] = array[array.length - 1];
            --array.length;
        }
    }

    /**
     * Verify array's Type
     * 验证数组的类型
     * @method verifyType
     * @param {array} array
     * @param {Function} type
     * @return {Boolean}
     */
    js.verifyType = function (array, type) {
        if (array && array.length > 0) {
            for (var i = 0; i < array.length; i++) {
                if (!(array[i] instanceof type)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Removes from array all values in minusArr. For each Value in minusArr, the first matching instance in array will be removed.
     * 从数组中移除minusArr中的所有值。对于minusArr中的每个值，数组中第一个匹配的实例将被删除。
     * @method removeArray
     * @param {Array} array Source Array
     * @param {Array} minusArr minus Array
     */
    js.removeArray = function (array, minusArr) {
        for (var i = 0, l = minusArr.length; i < l; i++) {
            this.remove(array, minusArr[i]);
        }
    }

    /**
     * Inserts some objects at index
     * 在索引处插入一些对象
     * @method appendObjectsAt
     * @param {Array} array
     * @param {Array} addObjs
     * @param {Number} index
     * @return {Array}
     */
    js.appendObjectsAt = function (array, addObjs, index) {
        array.splice.apply(array, [index, 0].concat(addObjs));
        return array;
    }

    /**
     * Determines whether the array contains a specific value.
     * 确定数组是否包含特定的值。
     * @method contains
     * @param {any[]} array
     * @param {any} value
     * @return {Boolean}
     */
    js.contains = function (array, value) {
        return array.indexOf(value) >= 0;
    }

    /**
     * Copy an array's item to a new array (its performance is better than Array.slice)
     * 将一个数组的项复制到一个新数组(其性能优于 Array.slice)
     * @method copyArray
     * @param {Array} array
     * @return {Array}
     */
    js.copyArray = function (array) {
        var i, len = array.length, arr_clone = new Array(len);
        for (i = 0; i < len; i += 1)
            arr_clone[i] = array[i];
        return arr_clone;
    }

    /**
     * Creates an object that has the specified prototype or that has null prototype.
     * @param o Object to use as a prototype. May be null.
     */
    js.createNullObject = function () {
        return Object.create(null);
    }

    exports.js = js;

}(window.ii = window.ii || {}));
//#endregion

//#region Base64
(function (exports) {
    class Base64 { }

    Base64.base64hash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

    // btoa method
    Base64.encode = function (s) {
        if (/([^\u0000-\u00ff])/.test(s)) {
            throw new Error('INVALID_CHARACTER_ERR');
        }
        var i = 0,
            prev,
            ascii,
            mod,
            result = [];

        while (i < s.length) {
            ascii = s.charCodeAt(i);
            mod = i % 3;
            switch (mod) {
                // 第一个6位只需要让8位二进制右移两位
                case 0:
                    result.push(Base64.base64hash.charAt(ascii >> 2));
                    break;
                //第二个6位 = 第一个8位的后两位 + 第二个8位的前4位
                case 1:
                    result.push(Base64.base64hash.charAt((prev & 3) << 4 | (ascii >> 4)));
                    break;
                //第三个6位 = 第二个8位的后4位 + 第三个8位的前2位
                //第4个6位 = 第三个8位的后6位
                case 2:
                    result.push(Base64.base64hash.charAt((prev & 0x0f) << 2 | (ascii >> 6)));
                    result.push(Base64.base64hash.charAt(ascii & 0x3f));
                    break;
            }
            prev = ascii;
            i++;
        }
        // 循环结束后看mod, 为0 证明需补3个6位，第一个为最后一个8位的最后两位后面补4个0。另外两个6位对应的是异常的“=”；
        // mod为1，证明还需补两个6位，一个是最后一个8位的后4位补两个0，另一个对应异常的“=”
        if (mod == 0) {
            result.push(Base64.base64hash.charAt((prev & 3) << 4));
            result.push('==');
        } else if (mod == 1) {
            result.push(Base64.base64hash.charAt((prev & 0x0f) << 2));
            result.push('=');
        }
        return result.join('');
    }

    // atob method
    // 逆转encode的思路即可
    Base64.decode = function (s) {
        s = s.replace(/\s|=/g, '');
        var cur,
            prev,
            mod,
            i = 0,
            result = [];

        while (i < s.length) {
            cur = Base64.base64hash.indexOf(s.charAt(i));
            mod = i % 4;
            switch (mod) {
                case 0:
                    break;
                case 1:
                    result.push(String.fromCharCode(prev << 2 | cur >> 4));
                    break;
                case 2:
                    result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2));
                    break;
                case 3:
                    result.push(String.fromCharCode((prev & 3) << 6 | cur));
                    break;
            }
            prev = cur;
            i++;
        }
        return result.join('');
    }

    exports.Base64 = Base64;

}(window.ii = window.ii || {}));
//#endregion

//#region Pool 对象池封装
(function (exports) {

    class Pool {
        /**
         * 根据对象类型标识字符，获取对象池。
         * @param sign 对象类型标识字符。
         * @return 对象池。
         */
        static getPoolBySign(sign) {
            return Pool._poolDic[sign] || (Pool._poolDic[sign] = []);
        }

        /**
         * 清除对象池的对象。
         * @param sign 对象类型标识字符。
         */
        static clearBySign(sign) {
            if (Pool._poolDic[sign])
                Pool._poolDic[sign].length = 0;
        }
        /**
         * 将对象放到对应类型标识的对象池中。
         * @param sign 对象类型标识字符。
         * @param item 对象。
         */
        static put(sign, item) {
            if (item[Pool.POOLSIGN])
                return;
            item[Pool.POOLSIGN] = true;
            Pool.getPoolBySign(sign).push(item);
        }

        /**
         * 根据类名进行回收，如果类有类名才进行回收，没有则不回收
         * @param instance 类的具体实例
         */
        static recoverByClass(instance) {
            if (instance) {
                var className = instance["__className"] || instance.constructor._$gid;
                if (className)
                    Pool.put(className, instance);
            }
        }
        /**
         * 返回类的唯一标识
         */
        static _getClassSign(cla) {
            var className = cla["__className"] || cla["_$gid"];
            if (!className) {
                cla["_$gid"] = className = Pool._CLSID + "";
                Pool._CLSID++;
            }
            return className;
        }
        /**
         * 根据类名回收类的实例
         * @param instance 类的具体实例
         */
        static createByClass(cls) {
            return Pool.getItemByClass(Pool._getClassSign(cls), cls);
        }
        /**
         * <p>根据传入的对象类型标识字符，获取对象池中此类型标识的一个对象实例。</p>
         * <p>当对象池中无此类型标识的对象时，则根据传入的类型，创建一个新的对象返回。</p>
         * @param sign 对象类型标识字符。
         * @param cls 用于创建该类型对象的类。
         * @return 此类型标识的一个对象。
         */
        static getItemByClass(sign, cls) {
            if (!Pool._poolDic[sign])
                return new cls();
            var pool = Pool.getPoolBySign(sign);
            if (pool.length) {
                var rst = pool.pop();
                rst[Pool.POOLSIGN] = false;
            }
            else {
                rst = new cls();
            }
            return rst;
        }
        /**
         * <p>根据传入的对象类型标识字符，获取对象池中此类型标识的一个对象实例。</p>
         * <p>当对象池中无此类型标识的对象时，则使用传入的创建此类型对象的函数，新建一个对象返回。</p>
         * @param sign 对象类型标识字符。
         * @param createFun 用于创建该类型对象的方法。
         * @param caller this对象
         * @return 此类型标识的一个对象。
         */
        static getItemByCreateFun(sign, createFun, caller = null) {
            var pool = Pool.getPoolBySign(sign);
            var rst = pool.length ? pool.pop() : createFun.call(caller);
            rst[Pool.POOLSIGN] = false;
            return rst;
        }
        /**
         * 根据传入的对象类型标识字符，获取对象池中已存储的此类型的一个对象，如果对象池中无此类型的对象，则返回 null 。
         * @param sign 对象类型标识字符。
         * @return 对象池中此类型的一个对象，如果对象池中无此类型的对象，则返回 null 。
         */
        static get(sign) {
            var pool = Pool.getPoolBySign(sign);
            var rst = pool.length ? pool.pop() : null;
            if (rst) {
                rst[Pool.POOLSIGN] = false;
            }
            return rst;
        }
    }
    Pool._CLSID = 0;
    Pool.POOLSIGN = "__InPool";
    Pool._poolDic = {};
    exports.Pool = Pool;

}(window.ii = window.ii || {}));
//#endregion

//#region Promise 封装
(function (exports) {
    var Coop = function (executor, onFullfiled, onRejected) {
        let promise = new Promise(executor);
        if (onFullfiled) {
            if (onRejected) {
                let isHandled = false;
                promise.then((result) => {
                    if (!isHandled) {
                        isHandled = true;
                        onFullfiled(result);
                    }
                }).catch((error) => {
                    ii.log.logWarn("coop error", error);
                    if (!isHandled) {
                        isHandled = true;
                        onRejected(error);
                    }
                });
            } else {
                promise.then(onFullfiled).catch((error) => {
                    ii.log.logWarn("coop rejected", error);
                });
            }
        } else if (onRejected) {
            promise.catch((error) => {
                ii.log.logWarn("coop error", error);
                onRejected(error);
            });
        } else {
            promise.catch((error) => {
                ii.log.logWarn("coop rejected", error);
            });
        }
        return promise;
    }
    exports.Coop = Coop;
}(window.ii = window.ii || {}));
//#endregion

//#region 高阶函数封装
(function (exports) {
    /**
     * 去除抖动
     * @param {Function} fun 
     * @param {number} duration 
     * @returns {number}
     */
    var debounce = function (fun, duration = 1) {
        let timeOutId;
        return function (...args) {
            timeOutId = setTimeout(() => {
                fun.apply(this, args);
            }, duration);
            return timeOutId;
        }
    }

    /**
     * 执行数据块
     * @param {*} datas 
     * @param {*} consumer 
     * @param {*} chunkSplitor 
     * @returns 
     */
    var performChunk = function (datas, consumer, chunkSplitor) {
        if (typeof datas === 'number') {
            datas = new Array(datas);
        }
        if (datas.length === 0) { return; }
        if (!chunkSplitor) {
        /*  if (globalThis.requestIdleCallback)
                chunkSplitor = (task) => {
                    requestIdleCallback((idle) => {
                        task(() => idle.timeRemaining() > 0);
                    });
                }
            else  */{
                chunkSplitor = (task) => {
                    setTimeout(() => {
                        task(time => time < 16);
                    }, 30)
                }
            }
        }
        //目前应该操作的任务下标
        let i = 0;
        //执行一块任务
        function _run() {
            if (i === datas.length) { return; }
            chunkSplitor((hasTime) => {
                const now = Date.now();
                while (hasTime(Date.now() - now) && i < datas.length) {
                    //在这一帧还有空闲时间
                    const item = datas[i];
                    consumer(item, i);
                    i++;
                }
                _run();
            });
        }
        _run();
        return datas;
    }
    /**
     * 
     * @param arr 
     * @param generateKey 
     * @returns 
     */
    var groupBy = function (arr, generateKey) {
        if (typeof generateKey === 'string') {
            const propName = generateKey;
            generateKey = (item) => item[propName];
        }
        const result = Object.create(null);
        for (const item of arr) {
            const key = generateKey(item);
            if (!result[key]) {
                result[key] = [];
            }
            result[key].push(item);
        }
        return result;
    }

    const _formatNormalize = function (formatter) {
        if (typeof formatter === "function") return formatter;
        if (typeof formatter !== "string")
            throw new TypeError("formatter must be a string");
        if (formatter === "date") {
            formatter = "yyyy-MM-dd";
        } else if (formatter === "datetime") {
            formatter = "yyyy-MM-dd HH:mm:ss";
        }
        const formatterFunc = (dateInfo) => {
            const { yyyy, MM, dd, HH, mm, ss, ms, w, q, a, A } = dateInfo;
            return formatter
                .replace("yyyy", yyyy)
                .replace("MM", MM)
                .replace("dd", dd)
                .replace("HH", HH)
                .replace("hh", HH)
                .replace("mm", mm)
                .replace("ss", ss)
                .replace("ms", ms)
                .replace("w", w)
                .replace("q", q)
                .replace("a", a)
                .replace("A", A)

        }
        return formatterFunc;
    }

    const formatterDate = function (date, formatter, isPad = false) {
        if (typeof date === "number" || typeof data === "string") date = new Date(date);
        formatter = _formatNormalize(formatter);
        const dateInfo = {
            year: date.getFullYear(),
            month: date.getMonth() + 1,
            day: date.getDate(),
            hour: date.getHours(),
            minute: date.getMinutes(),
            second: date.getSeconds(),
            week: date.getDay(),
            millisecond: date.getMilliseconds(),
            q: Math.floor((date.getMonth() + 3) / 3),//季度
            a: date.getHours() < 12 ? "上午" : "下午",//上下午
            A: date.getHours() < 12 ? "AM" : "PM",//AM/PM
        };
        const weekDay = ["日", "一", "二", "三", "四", "五", "六"];
        dateInfo.yyyy = dateInfo.year.toString();
        dateInfo.MM = dateInfo.month.toString();
        dateInfo.dd = dateInfo.day.toString();
        dateInfo.HH = dateInfo.hour.toString();
        dateInfo.mm = dateInfo.minute.toString();
        dateInfo.ss = dateInfo.second.toString();
        dateInfo.ms = dateInfo.millisecond.toString();
        dateInfo.w = weekDay[dateInfo.week];
        function _pad(prop, len) {
            dateInfo[prop] = dateInfo[prop].padStart(len, "0");
        }
        if (isPad) {
            _pad("yyyy", 4);
            _pad("MM", 2);
            _pad("dd", 2);
            _pad("HH", 2);
            _pad("mm", 2);
            _pad("ss", 2);
            _pad("ms", 3);
        }
        const result = formatter(dateInfo);
        return result;
    }

    exports.debounce = debounce;
    exports.performChunk = performChunk;
    exports.groupBy = groupBy;
    exports.formatterDate = formatterDate;

}(window.ii = window.ii || {}));
//#endregion

//#region cc.Node 的扩展
/*当前只有button widget 设置自动添加*/
cc.Node.prototype.findComponent = function (type) {
    return this.getComponent(type) || this.addComponent(type);
};
cc.Node.prototype.$ = function (target = 0, inChildrenSearch = false) {
    /**节点下标 */
    if (ii.js.isNumber(target)) {
        return this.children[target];
    }
    /*节点*/
    if (ii.js.isString(target)) {
        return cc.find(target, this);
        return this.getChildByName(target);
    }
    /*组件*/
    if (!inChildrenSearch) {
        return this.getComponent(target);
    }
    return this.getComponentInChildren(target);
};
cc.Node.prototype.hideChildren = function () {
    this.children.forEach(node => node.hide());
};
cc.Node.prototype.show = function () {
    this.active = true;
};
cc.Node.prototype.hide = function () {
    this.active = false;
};
cc.Node.prototype.getPhyCollider = function () {
    return this.$(cc.PhysicsBoxCollider) || this.$(cc.PhysicsCircleCollider) || this.$(cc.PhysicsPolygonCollider);
};
cc.Node.prototype.destroyAllChildrens = function (type) {
    this.getComponentsInChildren(type).forEach(value => value.node.destroy());
};
cc.Node.prototype.toggle = function () {
    this.active = !this.active;
};
cc.Node.prototype.setLabel = function (str) {
    let label = this.getComponent(cc.Label);
    label.string = str.toString();
};
cc.Node.prototype.setText = function (str) {
    let label = this.getComponent(cc.RichText);
    label.string = str.toString();
};
cc.Node.prototype.setSpriteFrame = function (spriteFrame) {
    let sprite = this.getComponent(cc.Sprite);
    if (spriteFrame instanceof cc.Texture2D)
        spriteFrame = new cc.SpriteFrame(spriteFrame)
    sprite.spriteFrame = spriteFrame;
};
cc.Node.prototype.setProgress = function (percent) {
    let progressBar = this.getComponent(cc.ProgressBar);
    progressBar.progress = percent;
};
cc.Node.prototype.onceClick = function (callback = null, btn_effect = 0) {
    this._click = false;
    var _self = this;
    this.once(ii.proxy.TouchEvent.touch_end, async function (e) {
        if (_self._click) return;
        _self._click = true;
        callback && await callback(e);
        _self._click = false;
        var btn = this.getComponent(cc.Button);
        btn && btn.destroy();
    });
    this.findComponent(cc.Button).transition = btn_effect ? cc.Button.Transition.COLOR : cc.Button.Transition.SCALE;
};
cc.Node.prototype.click = function (callback = null, btn_effect = 0) {
    this._click = false;
    var _self = this;
    this.on(ii.proxy.TouchEvent.touch_end, async function (e) {
        if (_self._click) return;
        _self._click = true;
        callback && await callback(e);
        _self._click = false;
    });
    this.findComponent(cc.Button).transition = btn_effect ? cc.Button.Transition.COLOR : cc.Button.Transition.SCALE;
};
cc.Node.prototype.clicks = function (callback = null, args = 'click') {
    this._click = false;
    var _self = this;
    this.on(ii.proxy.TouchEvent.touch_end, async function (e) {
        if (_self._click) return;
        _self._click = true;
        if (window["SoundManager"] && ii.js.isString(args)) {
            window["SoundManager"].playSound(args);
        }
        ii.js.isFunction(args) && args();
        callback && await callback(e);
        _self._click = false;
    });
    var button = this.findComponent(cc.Button);
    if (ii.js.isNumber(args)) {
        button.transition = args ? cc.Button.Transition.SCALE : cc.Button.Transition.COLOR;
    } else {
        button.transition = cc.Button.Transition.SCALE
    }
};
cc.Node.prototype.$click = function () {
    this.off(ii.proxy.TouchEvent.touch_end);
    var button = this.getComponent(cc.Button);
    button && button.destroy();
};
cc.Node.prototype.start = function (callback = null) {
    this.on(ii.proxy.TouchEvent.touch_start, callback);
};
cc.Node.prototype.$start = function () {
    this.off(ii.proxy.TouchEvent.touch_start);
};
cc.Node.prototype.move = function (callback = null) {
    this.on(ii.proxy.TouchEvent.touch_move, callback);
};
cc.Node.prototype.$move = function () {
    this.off(ii.proxy.TouchEvent.touch_move);
};
cc.Node.prototype.end = function (callback = null) {
    this.on(ii.proxy.TouchEvent.touch_end, callback);
};
cc.Node.prototype.$end = function () {
    this.off(ii.proxy.TouchEvent.touch_end);
};
cc.Node.prototype.$all = function () {
    this.off(ii.proxy.TouchEvent.touch_start);
    this.off(ii.proxy.TouchEvent.touch_move);
    this.off(ii.proxy.TouchEvent.touch_end);
};
//#endregion

(function () {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame =
            window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function (callback, element) {
            var currTime = Date.now();//new Date().getTime()
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function () {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        }

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
})();
